package edu.vt.ece.rt.benchmark;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import org.jgroups.Message;

import edu.vt.ece.rt.dstm.Context;
import edu.vt.ece.rt.dstm.ContextDelegator;
import edu.vt.ece.rt.quorum.ClustersManager;
import edu.vt.ece.rt.quorum.Resource;
import edu.vt.ece.rt.quorum.messsages.types.BenchCompletion;
import edu.vt.ece.rt.quorum.messsages.types.JoinCluster;
import edu.vt.ece.rt.quorum.messsages.types.PublishObject;
import edu.vt.ece.rt.quorum.util.Names;
import edu.vt.ece.rt.quorum.util.WaitObject;
import edu.vt.ece.rt.util.Logger.FileLogger;
import edu.vt.ece.rt.util.Logger.Logger;

public abstract class Benchmark {

	public final static int calls = Integer.getInteger(Names.calls);
	private static final Long delay=  Long.getLong(Names.transactionLength, 0);
	private static final Long TIMEOUT = Long.getLong(Names.timeOut);
	
	
	public static long timout() {
		return TIMEOUT + (int)(Math.random()*TIMEOUT);
	}

	public static void processingDelay(){
		try {
			Thread.sleep(delay);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	protected int localObjectsCount;
	protected int transactions;
	protected int readPercent;
	protected int threads;
	protected int aborts;
	protected int messages;
	protected int rootAborts;

	public Benchmark() {
		localObjectsCount = Integer.getInteger(Names.objects);
	}

	public void start() throws Throwable {
		String id = String.valueOf(ClustersManager.baseClusterId);

		Logger.debug("Creating Local Objects");
		
		createLocalObjects();
		Logger.debug("Created Local Objects");
		Logger.progress("Populated Objects", 10000*(1+(ClustersManager.nodesCount()/10)));

		long start = System.currentTimeMillis();
		transactions = Integer.getInteger(Names.transactions);
		readPercent = Integer.getInteger(Names.read);
		threads = Integer.getInteger(Names.threads);
		System.out.println("Transactions are "+transactions);
		BenchmarkThread[] testingThreads = new BenchmarkThread[threads];
		int readAborts = 0, writeAborts = 0, innerAborts = 0;
		//Create a new node Killer thread in parallel to all other benchmark thread

		for (int i=0; i<testingThreads.length; i++)
			testingThreads[i] = new BenchmarkThread(i, this);

		for (BenchmarkThread thread : testingThreads)
			thread.start();
		for (BenchmarkThread thread : testingThreads)
			thread.join();

		int reads=0, writes=0;
		for (BenchmarkThread thread : testingThreads){
			reads+=thread.reads;
			writes+=thread.writes;
			aborts+=thread.aborts;
			messages+=thread.messagesSent;
			rootAborts+=thread.rootAborts;
			readAborts += thread.readAborts;
			writeAborts += thread.writeAborts;
			innerAborts += thread.innerAborts;
		}
		
		/* Add messages sent by the remote side */
		messages+=Resource.getMessages();
		String header = 
				"  n="+ClustersManager.nodesCount() + 
				", t="+threads + 
				", o="+localObjectsCount +
				", x="+transactions + 
				", c="+calls + 
				", %="+readPercent +
				", T="+Integer.getInteger(Names.timeOut) +
				", C="+Integer.getInteger(Names.callCost) +
				", L="+Integer.getInteger(Names.transactionLength) +
				", K="+Integer.getInteger(Names.linkDelay) +
				".";
		String output = "";
		FileLogger.out.println(getLabel() + header);
		output = output + getLabel() + header+"\n";
		FileLogger.out.println("Throughput: " + ((float)1000 * transactions * threads / (System.currentTimeMillis()-start)));
		output = output +"Throughput: " + ((float)1000 * transactions * threads / (System.currentTimeMillis()-start)) +"\n";
		FileLogger.out.println("Reads: " + reads);
		output = output +"Reads: " + reads+"\n";
		FileLogger.out.println("Writes: " + writes);
		output = output +"Writes: " + writes+"\n";
		FileLogger.out.println("Aborts: " + aborts);
		FileLogger.out.println("Messages: " + messages);
		for(String line: result()) {
			FileLogger.out.println(line);
		}	
		FileLogger.out.flush();
		System.out.println("Root transaction aborts : "+rootAborts);
		System.out.println("Read transaction aborts : "+readAborts);
		System.out.println("Write transaction aborts : "+writeAborts);
		System.out.println("Inner transaction aborts : "+innerAborts);
		
//		try {
//			BufferedWriter out = new BufferedWriter(new FileWriter("/home/users/adityad/results/bank/node-"+Integer.getInteger(Names.nodes)+"/"+ClustersManager.baseClusterId+".result_new"));
//			out.write(output);
//			out.close();
//		} catch (IOException e) {
//		}
		
		// send all that i am done
		ClustersManager.getCluster(Names.base).send(new Message(null, new JoinCluster(null, true)));

		synchronized(Resource.waitObj) {
			while (Resource.nodesFinished != (Integer.getInteger(Names.nodes) - 1)) {
				System.out.println(ClustersManager.baseClusterId +" going for finish sleep");
				Resource.waitObj.wait();
				System.out.println(ClustersManager.baseClusterId +" waking up from finish sleep");
			}
		}

		checkSanity();
//		if (ClustersManager.baseClusterId == 0) {
//			checkSanity();
//		} else {	

			try {		//Give everyone some time to complete check sanity
				Thread.sleep(60000L);				//Increase the time, if sanity check requires talking to others too
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
//		}
//		int num = 0;
//		while (num < 10) {
//			Logger.debug("Sanity Check ...");
//			System.out.println("Just waiting around "+ClustersManager.baseClusterId);
////			if (ClustersManager.baseClusterId == 1)
////				checkSanity();
//			try {		//Give everyone some time to complete check sanity
//				Thread.sleep(60000L);				//Increase the time, if sanity check requires talking to others too
//			} catch (InterruptedException e) {
//				e.printStackTrace();
//			}
//			num++;
//		}
		
		//		for(StackTraceElement[] elements : Thread.getAllStackTraces().values())
		//			Logger.debug(Arrays.toString(elements));

		System.err.println("Complete...[" + id + "]");

		//		//TODO: Should it moved to QR.close()	??
		//		//Start waiting for others to complete
		//		if(ClustersManager.baseClusterId !=0){
		//			Logger.debug("Waiting for root done");
		//			while(ClustersManager.waitTillDone.rootDone.get() != true){
		//				System.out.print("- ");
		//				Thread.sleep(100);
		//			}			
		//		}else{//If you are root send everyone root Done update
		//			//Set auto-receive on if you are root node
		//			ClustersManager.getCluster(Names.base).setAutoReceive(true);
		//			ClustersManager.getCluster(Names.base).send(new Message(null, new BenchCompletion(true, false)));
		//			//wait for root done
		//			while(ClustersManager.waitTillDone.rootDone.get() != true){
		//				Thread.sleep(100);
		//			}	
		//		}
		//		
		//		//Root is done, I am also done, now reply
		//		WaitObject w = ClustersManager.waitTillDone;
		//		synchronized (w) {
		//			Logger.debug("Waiting for root exit message");
		//			ClustersManager.getCluster(Names.base).send(new Message(w.rootAdr, new BenchCompletion(false, false)));		//Unicast back to root
		//			w.wait();
		//		}
		//		
		//		
		//		if(ClustersManager.baseClusterId == 0){
		//			//Set auto receive off, default behaviour of base cluster
		//			ClustersManager.getCluster(Names.base).setAutoReceive(false);
		//		}

//		if(Boolean.getBoolean(Names.sanity)) {
//			Logger.debug("Sanity Check ...");
//			checkSanity();
//			try {		//Give everyone some time to complete check sanity
//				Thread.sleep(500);				//Increase the time, if sanity check requires talking to others too
//			} catch (InterruptedException e) {
//				e.printStackTrace();
//			}
//		}

	}	

	protected String[] result() {
		return new String[] {
				"",
				//				"Conflicts: " + ContentionPolicy.dist_conflicts,
				//				"Local Conflicts: " + ContentionPolicy.conflicts,
		};
	}

	protected void configure(String args[]){}

	abstract protected Object randomId();
	abstract protected int getOperandsCount();

	abstract protected void readOperation(Object... ids);
	abstract protected void writeOperation(Object... ids);
	abstract protected void checkSanity();

	abstract protected void createLocalObjects();

	abstract protected String getLabel();


}
